package br.com.obardavez.util.impl;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.persistence.Column;
import javax.persistence.JoinColumn;

import br.com.obardavez.util.BeanUtil;
import br.com.obardavez.util.Util;

import com.google.inject.Inject;

public class BeanUtilImpl implements BeanUtil {

	
	private static final long serialVersionUID = 1L;
	
	@Inject
	private Util util;
	
	@Override
	public Boolean instanceOf(Class<?> beanClass, Class<?> superType) {
		Boolean result = Boolean.FALSE;
		if (beanClass.getInterfaces() != null && beanClass.getInterfaces().length > 0) {
			for(int i = 0; i < beanClass.getInterfaces().length; i++) {
				if (beanClass.getInterfaces()[i].equals(superType)) {
					result = Boolean.TRUE;
					break;
				}
			}
		}
		return result;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getGenericAttr(java.lang.Object)
	 */
	@Override
	public Class<?> getGenericAttr(Object obj) {
		return getGenericAttr(obj.getClass());
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getGenericAttr(java.lang.Class)
	 */
	@Override
	public Class<?> getGenericAttr(Class<?> objClass) {
		return getGenericAttr(objClass, 0);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getGenericAttr(java.lang.Object,
	 * java.lang.Integer)
	 */
	@Override
	public Class<?> getGenericAttr(Object obj, Integer indice) {
		return getGenericAttr(obj.getClass(), indice);
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getGenericAttr(java.lang.Class,
	 * java.lang.Integer)
	 */
	@Override
	public Class<?> getGenericAttr(Class<?> objClass, Integer indice) {

		String nameClass = "";
		Class<?> clazz = null;

		ParameterizedType paramType = null;

		if (objClass.getGenericSuperclass() instanceof ParameterizedType) {

			paramType = (ParameterizedType) objClass.getGenericSuperclass();

			if (paramType.getActualTypeArguments() == null
					|| paramType.getActualTypeArguments().length <= indice) {
				throw new RuntimeException("Nao e possivel recuperar o "
						+ "atributo generico com o indice informado");
			}

			nameClass = paramType.getActualTypeArguments()[indice].toString()
					.replaceAll("class ", "").replaceAll("interface ", "");

			try {
				clazz = (Class<?>) Class.forName(nameClass);
			} catch (Exception e) {
				throw new RuntimeException(
						"Erro ao criar instancia do elemento espacial.");
			}
		}

		if (clazz == null && !objClass.equals(Object.class)) {
			clazz = getGenericAttr(objClass.getSuperclass(), indice);
		}
		
		return clazz;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getPropertyFromGetter(java.lang
	 * .String)
	 */
	@Override
	public String getPropertyFromGetter(String methodName) {
		String prop = methodName.substring(3, methodName.length());
		prop = prop.substring(0, 1).toLowerCase() + prop.substring(1);
		return prop;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getPropertyFromSetter(java.lang
	 * .String)
	 */
	@Override
	public String getPropertyFromSetter(String methodName) {
		String prop = methodName.substring(3, methodName.length());
		prop = prop.substring(0, 1).toLowerCase() + prop.substring(1);
		return prop;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#isGetter(java.lang.reflect.Method
	 * )
	 */
	@Override
	public Boolean isGetter(Method method) {
		return method != null && method.getName().startsWith("get")
				&& method.getReturnType() != null;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#isSetter(java.lang.reflect.Method
	 * )
	 */
	@Override
	public Boolean isSetter(Method method) {
		return method != null && method.getName().startsWith("set")
				&& method.getReturnType().equals(void.class)
				&& method.getParameterTypes() != null
				&& method.getParameterTypes().length == 1;
	}

	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getSetterMethodFromProperty(java
	 * .lang.Class, java.lang.String)
	 */
	@Override
	public Method getSetterMethodFromProperty(Class<?> clazz,
			String propertyName) {

		Method setter = null;

		if (clazz != null && util.getStringUtil().isNotEmpty(propertyName)) {
			for (Method method : clazz.getDeclaredMethods()) {
				if (isSetter(method)
						&& getPropertyFromSetter(method.getName()).equals(
								propertyName)) {
					setter = method;
					break;
				}
			}
		}

		return setter;
	}
	
	/*
	 * (non-Javadoc)
	 * 
	 * @see
	 * com.digicade.android.util.impl.BeanUtil#getGetterMethodFromProperty(java
	 * .lang.Class, java.lang.String)
	 */
	@Override
	public Method getGetterMethodFromProperty(Class<?> clazz,
			String propertyName) {
		
		Method getter = null;
		
		if (clazz != null && util.getStringUtil().isNotEmpty(propertyName)) {
			for (Method method : clazz.getDeclaredMethods()) {
				if (isGetter(method)
						&& getPropertyFromGetter(method.getName()).equals(
								propertyName)) {
					getter = method;
					break;
				}
			}
		}
		
		return getter;
	}
	
	public String getPropertyName(Class<?> entityClass,
			Class<? extends Annotation> annotationClass) {

		String propName = getPropertyNameInMethod(entityClass, annotationClass);
		if (util.getStringUtil().isEmpty(propName)) {
			propName = getPropertyNameInProperty(entityClass, annotationClass);
		}
		return propName;
	}
	
	public String getPropertyName(Class<?> entityClass, String columnName) {

		String propName = null;
		Class<?> clazz = entityClass;

		while (util.getStringUtil().isEmpty(propName)
				&& !clazz.equals(Object.class)) {

			for (Method method : clazz.getDeclaredMethods()) {

				if (isGetter(method)) {

					String methodProperty = getPropertyFromGetter(method
							.getName());
					String columnMethodProperty = getColumnName(clazz,
							methodProperty);

					if (util.getStringUtil().isNotEmpty(columnMethodProperty)
							&& columnMethodProperty.toUpperCase().equals(
									columnName)) {
						propName = methodProperty;
						break;
					}
				}
			}

			clazz = clazz.getSuperclass();
		}

		return propName;
	}

	public List<String> getNotEmptyPropertyNames(Object instance){
		try {
			Class<?> cls = Class.forName(instance.getClass().getName());
			List<String> propertyNames = new ArrayList<String>();
			Field fieldlist[] = cls.getDeclaredFields();
			for (int i = 0; i < fieldlist.length; i++) {
				Field field = fieldlist[i];
				Object obj = getPropertyValue(instance, field.getName());
				if(obj != null && (!(obj instanceof String) || !obj.toString().equals(""))
						&& (!(obj instanceof List) || util.getCollectionUtil().isNotEmpty((List<?>)obj))){
					propertyNames.add(field.getName());
				}
	
			}
			return propertyNames;
		} catch (Throwable e) {
			System.err.println(e);
			throw new RuntimeException();
		}
	}
	
	public <T  extends Annotation> T getAnnotationInType(Object obj,
			Class<T> annotationClass) {

		Class<?> objClass = obj.getClass();

		T annotation = getAnnotationInType(objClass, obj, annotationClass);

		if (annotation == null && !objClass.getSuperclass().equals(Object.class)) {
			return getAnnotationInType(objClass.getSuperclass(), obj,
					annotationClass);

		}

		return annotation;
	}
	
	private <T  extends Annotation> T getAnnotationInType(Class<?> objClass, Object obj,
			Class<T> annotationClass) {
		
		T annotation = objClass.getAnnotation(annotationClass);
		
		return annotation;
		
	}

	public Object getPropertyValue(Object obj,
			Class<? extends Annotation> annotationClass) {
		
		Class<?> objClass = obj.getClass();
		
		Object propValue = getPropertyValue(objClass, obj, annotationClass);
		
		if (propValue == null && !objClass.getSuperclass().equals(Object.class)) {
			return getPropertyValue(objClass.getSuperclass(), obj,
					annotationClass);
			
		}
		
		return propValue;
	}
	
	public Object getPropertyValue(Object obj, String propertyName) {

		Class<?> objClass = obj.getClass();

		Object propValue = getPropertyValue(objClass, obj, propertyName);

		if (propValue == null && !objClass.getSuperclass().equals(Object.class)) {
			return getPropertyValue(objClass.getSuperclass(), obj, propertyName);

		}

		return propValue;
	}
	
	public <T> List<T> getPropertyValues(List<? extends Object> objs,
			Class<? extends Annotation> annotationClass, Class<T> returnType) {

		List<T> values = new ArrayList<T>();
		if (objs != null) {
			for (Object obj : objs) {
				@SuppressWarnings("unchecked")
				T value = (T) getPropertyValue(obj, annotationClass);
				if (value != null) {
					values.add(value);
				}
			}
		}

		return values;
	}
	
	public String getColumnName(Object obj,
			Class<? extends Annotation> annotationClass) {

		Class<?> objClass = obj.getClass();

		String columnName = getColumnName(objClass, annotationClass);

		if (columnName == null
				&& !objClass.getSuperclass().equals(Object.class)) {
			return getColumnName(objClass.getSuperclass(), annotationClass);
		}

		return columnName;
	}

	public String getColumnName(Class<?> objClass,
			Class<? extends Annotation> annotationClass) {

		String columnName = getColumnNameInMethod(objClass, annotationClass);
		if (util.getStringUtil().isEmpty(columnName)) {
			columnName = getColumnNameInProperty(objClass, annotationClass);
		}
		return columnName;
	}
	
	public String getColumnName(Class<?> objClass, String fieldName) {

		String columnName = null;

		try {
			Field field = objClass.getDeclaredField(fieldName);
			if (field != null && field.isAnnotationPresent(Column.class)) {
				columnName = field.getAnnotation(Column.class).name();
			} else if (field != null
					&& field.isAnnotationPresent(JoinColumn.class)) {
				columnName = field.getAnnotation(JoinColumn.class).name();
			} else {
				for (Method method : objClass.getDeclaredMethods()) {

					if (isGetter(method)
							&& getPropertyFromGetter(method.getName()).equals(
									fieldName)) {

						if (method.isAnnotationPresent(Column.class)) {
							columnName = method.getAnnotation(Column.class)
									.name();
						} else if (method.isAnnotationPresent(JoinColumn.class)) {
							columnName = method.getAnnotation(JoinColumn.class)
									.name();
						}

						break;
					}
				}
			}
		} catch (SecurityException e) {
			e.printStackTrace();
			throw new RuntimeException("Error retrieving column name: "
					+ e.getMessage());
		} catch (NoSuchFieldException e) {
			e.printStackTrace();
			throw new RuntimeException("Error retrieving column name: "
					+ e.getMessage());
		}

		if (columnName == null
				&& !objClass.getSuperclass().equals(Object.class)) {
			return getColumnName(objClass.getSuperclass(), columnName);
		}

		return columnName;
	}
	
	public void setPropertyValue(Object obj, String propertyName, Object value) {

		if (obj != null && util.getStringUtil().isNotEmpty(propertyName)) {

			try {

				Method setter = getSetterMethodFromProperty(obj.getClass(),
						propertyName);
				setter.invoke(obj, new Object[] { value });

			} catch (IllegalArgumentException e) {
				e.printStackTrace();
				throw new RuntimeException("Error setting value.");
			} catch (IllegalAccessException e) {
				e.printStackTrace();
				throw new RuntimeException("Error setting value.");
			} catch (InvocationTargetException e) {
				e.printStackTrace();
				throw new RuntimeException("Error setting value.");
			}
		}
	}
	
	/**
	 * Recupera o valor da propriedade informada a partir do metodo getter na
	 * classe informada
	 * 
	 * @param clazz
	 *            Classe que cont�m o m�todo getter
	 * @param obj
	 *            Objeto para o qual o m�todo ser� executado
	 * @param propertyName
	 *            Nome da propriedade
	 * @return Valor da propriedade
	 */
	private Object getPropertyValue(Class<?> clazz, Object obj,
			String propertyName) {

		Object propValue = null;

		for (Method method : clazz.getDeclaredMethods()) {

			if (isGetter(method)
					&& getPropertyFromGetter(method.getName()).equals(
							propertyName)) {

				try {
					propValue = method.invoke(obj, new Object[0]);
					break;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(
							"Erro ao recuperar valor da propriedade: "
									+ e.getMessage());
				}
			}
		}

		return propValue;
	}
	
	private String getColumnNameInMethod(Class<?> objClass,
			Class<? extends Annotation> annotationClass) {

		String columnName = null;

		for (Method method : objClass.getDeclaredMethods()) {

			if (method.isAnnotationPresent(annotationClass) && isGetter(method)) {

				try {
					String fieldName = getPropertyFromGetter(method.getName());
					columnName = getColumnName(objClass, fieldName);
					break;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(
							"Erro ao recuperar nome da coluna: "
									+ e.getMessage());
				}
			}
		}

		return columnName;
	}
	
	private String getColumnNameInProperty(Class<?> objClass,
			Class<? extends Annotation> annotationClass) {

		String columnName = null;

		for (Field field : objClass.getDeclaredFields()) {

			if (field.isAnnotationPresent(annotationClass)) {

				try {
					columnName = getColumnName(objClass, field.getName());
					break;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(
							"Erro ao recuperar nome da coluna: "
									+ e.getMessage());
				}
			}
		}

		return columnName;
	}

	private Object getPropertyValue(Class<?> objClass, Object obj,
			Class<? extends Annotation> annotationClass) {

		Object propertyValue = getPropertyValueInMethod(objClass, obj,
				annotationClass);
		if (propertyValue == null) {
			propertyValue = getPropertyValueInProperty(objClass, obj,
					annotationClass);
		}
		return propertyValue;

	}
	
	/**
	 * Procura um m�todo da classe informada que possui a anota��o e
	 * recupera o valor retornado pelo m�todo anotado
	 * 
	 * @param clazz
	 *            Classe que cont�m o m�todo anotado
	 * @param obj
	 *            Objeto para o qual o m�todo ser� executado
	 * @param annotationClass
	 *            Anota��o do m�todo
	 * @return Valor retornado pelo m�todo anotado
	 */
	private Object getPropertyValueInMethod(Class<?> clazz, Object obj,
			Class<? extends Annotation> annotationClass) {

		Object propValue = null;

		for (Method method : clazz.getDeclaredMethods()) {

			if (method.isAnnotationPresent(annotationClass) && isGetter(method)) {

				try {
					propValue = method.invoke(obj, new Object[0]);
					break;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(
							"Erro ao recuperar valor da propriedade: "
									+ e.getMessage());
				}
			}
		}

		return propValue;
	}
	
	private Object getPropertyValueInProperty(Class<?> clazz, Object obj,
			Class<? extends Annotation> annotationClass) {

		Object propValue = null;

		for (Field field : clazz.getDeclaredFields()) {

			if (field.isAnnotationPresent(annotationClass)) {

				try {
					field.setAccessible(true);
					propValue = field.get(obj);
					break;
				} catch (Exception e) {
					e.printStackTrace();
					throw new RuntimeException(
							"Erro ao recuperar valor da propriedade: "
									+ e.getMessage());
				}
			}
		}

		return propValue;
	}
	
	private String getPropertyNameInMethod(Class<?> entityClass,
			Class<? extends Annotation> annotationClass) {

		String propName = null;
		Class<?> clazz = entityClass;

		while (util.getStringUtil().isEmpty(propName)
				&& !clazz.equals(Object.class)) {

			for (Method method : clazz.getDeclaredMethods()) {

				if (method.isAnnotationPresent(annotationClass)
						&& isGetter(method)) {

					try {
						propName = getPropertyFromGetter(method.getName());
						break;
					} catch (Exception e) {
						e.printStackTrace();
						throw new RuntimeException(
								"Erro ao recuperar nome da propriedade: "
										+ e.getMessage());
					}
				}
			}

			clazz = clazz.getSuperclass();
		}

		return propName;
	}
	
	private String getPropertyNameInProperty(Class<?> entityClass,
			Class<? extends Annotation> annotationClass) {

		String propName = null;
		Class<?> clazz = entityClass;

		while (util.getStringUtil().isEmpty(propName)
				&& !clazz.equals(Object.class)) {

			for (Field field : clazz.getDeclaredFields()) {

				if (field.isAnnotationPresent(annotationClass)) {

					try {
						propName = field.getName();
						break;
					} catch (Exception e) {
						e.printStackTrace();
						throw new RuntimeException(
								"Erro ao recuperar nome da propriedade: "
										+ e.getMessage());
					}
				}
			}

			clazz = clazz.getSuperclass();
		}

		return propName;
	}

	@Override
	public <E> List<E> generateList(Class<E> entityClass,
			List<Map<String, Object>> items) {
		List<E> entities = new ArrayList<E>();
		E entity = null;

		if (util.getCollectionUtil().isNotEmpty(items)) {
			for (Map<String, Object> item : items) {
				try {
					entity = entityClass.newInstance();
					for (String field : item.keySet()) {
						Object value = item.get(field);
						Method setter = getSetterMethodFromProperty(
								entityClass, field);
						setter.invoke(entity, new Object[] { value });
					}
					if (!entities.contains(entity)) {
						entities.add(entity);
					}
				} catch (InstantiationException e) {
					e.printStackTrace();
					throw new RuntimeException("Error creating entity.", e);
				} catch (IllegalAccessException e) {
					e.printStackTrace();
					throw new RuntimeException("Error creating entity.", e);
				} catch (IllegalArgumentException e) {
					e.printStackTrace();
					throw new RuntimeException("Error invoking setter method.",
							e);
				} catch (InvocationTargetException e) {
					e.printStackTrace();
					throw new RuntimeException("Error invoking setter method.",
							e);
				}
			}
		}

		return entities;
	}
}
